﻿using System;
using System.Collections.Generic;
using Jayden.Dll.Cards.Enums;

namespace Jayden.Dll.Cards
{
	public class Card : IComparable<Card>
	{
		private static Dictionary<string, Card> m_CardsByShortString;
		private static Dictionary<ulong, Card> m_CardsByMask;
		private static readonly Card[] m_Cards;
		public static IEnumerable<Card> Cards { get { return m_Cards; } }
		public static IEnumerable<Card> GetSuits(CardSuit suit)
		{
			foreach (Card card in Cards)
				if (card.Suit == suit)
					yield return card;
		}
		public static IEnumerable<Card> GetRanks(CardRank rank)
		{
			foreach (Card card in Cards)
				if (card.Rank == rank)
					yield return card;
		}

		public static IEnumerable<CardSuit> Suits
		{
			get
			{
				yield return CardSuit.Clubs;
				yield return CardSuit.Diamonds;
				yield return CardSuit.Hearts;
				yield return CardSuit.Spades;
			}
		}
		public static IEnumerable<CardRank> Ranks
		{
			get
			{
				yield return CardRank.Two;
				yield return CardRank.Three;
				yield return CardRank.Four;
				yield return CardRank.Five;
				yield return CardRank.Six;
				yield return CardRank.Seven;
				yield return CardRank.Eight;
				yield return CardRank.Nine;
				yield return CardRank.Ten;
				yield return CardRank.Jack;
				yield return CardRank.Queen;
				yield return CardRank.King;
				yield return CardRank.Ace;
}
		}

		static Card()
		{
			List<Card> cards = new List<Card>();
			m_CardsByShortString = new Dictionary<string, Card>();
			m_CardsByMask = new Dictionary<ulong, Card>();
			foreach (CardSuit suit in Enum.GetValues(typeof(CardSuit)))
			{
				foreach (CardRank rank in Enum.GetValues(typeof(CardRank)))
				{
					Card card = new Card(rank, suit);
					cards.Add(card);
					m_CardsByShortString.Add(card.ToShortString(), card);
					m_CardsByMask.Add(card.Mask, card);
#if DEBUG
					uint bp = card.BitPosition;
					ulong mask = 1ul << (int)bp;
					if (mask != card.Mask)
						throw new Exception("invalid bitposition.");
#endif
				}
			}
			m_Cards = cards.ToArray();
		}

		public static implicit operator Card(string value) { return Card.Parse(value); }

		public static Card Parse(string cardString)
		{
			Card result;
			if (m_CardsByShortString.TryGetValue(cardString, out result))
				return result;
			foreach (Card card in Cards)
				if (card.ToShortString() == cardString)
					return card;
			throw new ArgumentException("Invalid card.", "cardString");
		}
		public static Card Parse(ulong mask) { return m_CardsByMask[mask]; }
		public static Card[] ParseArray(string cardsString, char separator = ' ')
		{
			if (string.IsNullOrWhiteSpace(cardsString))
				return new Card[0];
			string[] cardsStringArray = cardsString.Split(separator);
			Card[] cards = new Card[cardsStringArray.Length];
			for (int c = 0; c < cards.Length; c++)
				cards[c] = Parse(cardsStringArray[c]);
			return cards;
		}

		public static CardRank ParseRank(char rank)
		{
			switch (rank)
			{
				case '2':
					return CardRank.Two;
				case '3':
					return CardRank.Three;
				case '4':
					return CardRank.Four;
				case '5':
					return CardRank.Five;
				case '6':
					return CardRank.Six;
				case '7':
					return CardRank.Seven;
				case '8':
					return CardRank.Eight;
				case '9':
					return CardRank.Nine;
				case 'T':
					return CardRank.Ten;
				case 'J':
					return CardRank.Jack;
				case 'Q':
					return CardRank.Queen;
				case 'K':
					return CardRank.King;
				case 'A':
					return CardRank.Ace;
			}
			throw new Exception("Unknow rank: " + rank);
		}

		public const int Count = 52;

		private CardRank m_Rank;
		private CardSuit m_Suit;

		private Card(CardRank rank, CardSuit suit)
		{
			m_Rank = rank;
			m_Suit = suit;
		}
		private Card(ulong mask)
		{
			uint bitPos = 0;
			while (mask > 0)
			{
				if (mask == 1)
				{
					uint suit = bitPos / 13;
					uint rank = bitPos % 13;
					m_Suit = (CardSuit)suit;
					m_Rank = (CardRank)rank;
					return;
				}
				mask = (mask >> 1);
				bitPos++;
			}
			throw new Exception("Invalid card mask: " + mask);
		}

		public CardRank Rank { get { return m_Rank; } }
		public CardSuit Suit { get { return m_Suit; } }

		public static uint GetBitPosition(CardRank cardRank, CardSuit cardSuit)
		{
			uint suit = (uint)cardSuit;
			uint rank = (uint)cardRank;
			uint bitPos = (rank + suit * 13);
			return bitPos;
		}
		public static ulong GetMask(CardRank cardRank, CardSuit cardSuit) { return ((ulong)1) << (int)GetBitPosition(cardRank, cardSuit); }
		public static Card GetCard(CardRank cardRank, CardSuit cardSuit) { return Parse(GetMask(cardRank, cardSuit)); }
		public uint BitPosition { get { return GetBitPosition(m_Rank, m_Suit); } }
		public ulong Mask { get { return GetMask(m_Rank, m_Suit); } }

		public override bool Equals(object obj)
		{
			if (obj is Card)
			{
				if (object.ReferenceEquals(this, obj))
					return true;
				Card card = obj as Card;
				if (card.Rank.Equals(m_Rank) && card.Suit.Equals(m_Suit))
					return true;
			}
			return false;
		}
		public override int GetHashCode()
		{
			return m_Rank.GetHashCode() * 31 + m_Suit.GetHashCode();
		}
		public override string ToString()
		{
			return string.Format("{0} of {1}", m_Rank, m_Suit);
		}
		public int CompareTo(Card other)
		{
			return -m_Rank.CompareTo(other.Rank);
		}
		public static string GetRankPairName(CardRank rank)
		{
			switch (rank)
			{
				case CardRank.Two:
					return "Deuces";
				case CardRank.Six:
					return "Sixes";
				default:
					return rank.ToString() + "s";
			}
		}
		public static char GetRankChar(CardRank rank)
		{
			switch (rank)
			{
				case CardRank.Two:
					return '2';
				case CardRank.Three:
					return '3';
				case CardRank.Four:
					return '4';
				case CardRank.Five:
					return '5';
				case CardRank.Six:
					return '6';
				case CardRank.Seven:
					return '7';
				case CardRank.Eight:
					return '8';
				case CardRank.Nine:
					return '9';
				case CardRank.Ten:
					return 'T';
				case CardRank.Jack: 
					return 'J';
				case CardRank.Queen:
					return 'Q';
				case CardRank.King:
					return 'K';
				case CardRank.Ace:
					return 'A';
			}
			throw new Exception("Unknow rank: " + rank);
		}
		public static string GetRanks(params CardRank[] ranks)
		{
			if (ranks == null || ranks.Length == 0)
				return null;
			string result = "";
			foreach (CardRank r in ranks)
				result += GetRankChar(r);
			return result;
		}
		public char RankChar { get { return GetRankChar(m_Rank); } }
		public char SuitChar
		{
			get
			{
				switch (m_Suit)
				{
					case CardSuit.Hearts:
						return 'h';
					case CardSuit.Diamonds:
						return 'd';
					case CardSuit.Clubs:
						return 'c';
					case CardSuit.Spades:
						return 's';
				}
				throw new Exception("Unknow suit: " + m_Suit);
			}
		}
		public char SuitIcon
		{
			get
			{ 
				switch (m_Suit)
				{
					case CardSuit.Hearts:
						return '♥';
					case CardSuit.Diamonds:
						return '♦';
					case CardSuit.Clubs:
						return '♣';
					case CardSuit.Spades:
						return '♠';
				}
				throw new Exception("Unknow suit: " + m_Suit);
			}
		}
		public string SuitHtml
		{
			get
			{
				switch (m_Suit)
				{
					case CardSuit.Hearts:
						return "&hearts;";
					case CardSuit.Diamonds:
						return "&diams;";
					case CardSuit.Clubs:
						return "&clubs;";
					case CardSuit.Spades:
						return "&spades;";
				}
				throw new Exception("Unknow suit: " + m_Suit);
			}
		}
		public string ToShortString() { return string.Format("{0}{1}", RankChar, SuitChar); }

	}

	public sealed class CardBit
	{
		private CardBit() { }

		public const int NumberOfCards = 52;
		public const int NumberOfCardsBySuit = 13;
		public const int NumberOfSuits = 4;

		public const ulong SuitMask = 0x1fffUL;
		public const uint CardMask = 0xf;

		public const uint StraightFlushValue = 8 << HandTypeOffset;
		public const uint FourOfAKindValue = 7 << HandTypeOffset;
		public const uint FullHouseValue = 6 << HandTypeOffset;
		public const uint FlushValue = 5 << HandTypeOffset;
		public const uint StraightValue = 4 << HandTypeOffset;
		public const uint ThreeOfAKindValue = 3 << HandTypeOffset;
		public const uint TwoPairsValue = 2 << HandTypeOffset;
		public const uint OnePairValue = 1 << HandTypeOffset;
		public const uint HighCardValue = 0 << HandTypeOffset;

		public const int HeartsOffset = (int)CardSuit.Hearts * NumberOfCardsBySuit;
		public const int DiamondsOffset = (int)CardSuit.Diamonds * NumberOfCardsBySuit;
		public const int ClubsOffset = (int)CardSuit.Clubs * NumberOfCardsBySuit;
		public const int SpadesOffset = (int)CardSuit.Spades * NumberOfCardsBySuit;

		public const int CardWidth = 4;
		public const int HandTypeOffset = 24;
		public const int TopCardOffset = 16;
		public const int SecondCardOffset = 12;
		public const int ThirdCardOffset = 8;
		public const int FourthCardOffset = 4;
		public const int FifthCardOffset = 0;

		public const uint TopCardMask = CardMask << TopCardOffset;
		public const uint SecondCardMask = CardMask << SecondCardOffset;

		private static uint[] TopFiveCardsTable;
		private static uint[] TopCardsTable;
		private static byte[] RankBitTable;
		private static ulong[] CardMaskTable;
		private static byte[] StraightTable;

		public static int NumberOfSetBits(ulong i)
		{
			i = i - ((i >> 1) & 0x5555555555555555UL);
			i = (i & 0x3333333333333333UL) + ((i >> 2) & 0x3333333333333333UL);
			return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56);
		}

		public static uint HandType(uint handValue) { return (handValue >> HandTypeOffset); }

		public static IEnumerable<ulong> Hands(ulong shared, ulong dead, int numberOfCards)
		{
			int i1, i2, i3, i4, i5, i6, i7, length;
			ulong c1, c2, c3, c4, c5, c6, c7;
			ulong n2, n3, n4, n5, n6;
			int bitCount = NumberOfSetBits(shared);
			dead |= shared;
#region Loop
			switch (numberOfCards - bitCount)
			{
				case 0:
					yield return shared;
					break;
				case 1:
					for (i1 = NumberOfCards - 1; i1 >= 0; i1--)
					{
						c1 = CardMaskTable[i1];
						if ((dead & c1) != 0)
							continue;
						yield return c1 | shared;
					}
					break;
				case 2:
					for (i1 = NumberOfCards - 1; i1 >= 0; i1--)
					{
						c1 = CardMaskTable[i1];
						if ((dead & c1) != 0)
							continue;
						for (i2 = i1 - 1; i2 >= 0; i2--)
						{
							c2 = CardMaskTable[i2];
							if ((dead & c2) != 0)
								continue;
							yield return c1 | c2 | shared;
						}
					}
					break;
				case 3:
					for (i1 = NumberOfCards - 1; i1 >= 0; i1--)
					{
						c1 = CardMaskTable[i1];
						if ((dead & c1) != 0)
							continue;
						for (i2 = i1 - 1; i2 >= 0; i2--)
						{
							c2 = CardMaskTable[i2];
							if ((dead & c2) != 0)
								continue;
							n2 = c1 | c2;
							for (i3 = i2 - 1; i3 >= 0; i3--)
							{
								c3 = CardMaskTable[i3];
								if ((dead & c3) != 0)
									continue;
								yield return n2 | c3 | shared;
							}
						}
					}
					break;
				case 4:
					for (i1 = NumberOfCards - 1; i1 >= 0; i1--)
					{
						c1 = CardMaskTable[i1];
						if ((dead & c1) != 0)
							continue;
						for (i2 = i1 - 1; i2 >= 0; i2--)
						{
							c2 = CardMaskTable[i2];
							if ((dead & c2) != 0)
								continue;
							n2 = c1 | c2;
							for (i3 = i2 - 1; i3 >= 0; i3--)
							{
								c3 = CardMaskTable[i3];
								if ((dead & c3) != 0)
									continue;
								n3 = n2 | c3;
								for (i4 = i3 - 1; i4 >= 0; i4--)
								{
									c4 = CardMaskTable[i4];
									if ((dead & c4) != 0)
										continue;
									yield return n3 | c4 | shared;
								}
							}
						}
					}
					break;
				case 5:
					for (i1 = NumberOfCards - 1; i1 >= 0; i1--)
					{
						c1 = CardMaskTable[i1];
						if ((dead & c1) != 0)
							continue;
						for (i2 = i1 - 1; i2 >= 0; i2--)
						{
							c2 = CardMaskTable[i2];
							if ((dead & c2) != 0)
								continue;
							n2 = c1 | c2;
							for (i3 = i2 - 1; i3 >= 0; i3--)
							{
								c3 = CardMaskTable[i3];
								if ((dead & c3) != 0)
									continue;
								n3 = n2 | c3;
								for (i4 = i3 - 1; i4 >= 0; i4--)
								{
									c4 = CardMaskTable[i4];
									if ((dead & c4) != 0)
										continue;
									n4 = n3 | c4;
									for (i5 = i4 - 1; i5 >= 0; i5--)
									{
										c5 = CardMaskTable[i5];
										if ((dead & c5) != 0)
											continue;
										yield return n4 | c5 | shared;
									}
								}
							}
						}
					}
						break;
				default:
					throw new NotImplementedException();
			}
#endregion
		}
		public static IEnumerable<ulong> TexasHoldemClosedCards() { return Hands(0x00, 0x00, 2); }

		public static uint Evaluate(ulong cards, int numberOfCards)
		{
			uint result = 0, fourMask, threeMask, twoMask, tmp, tmp2, kickers;
			uint straight = 0;
#if DEBUG
			// This functions supports 1-7 cards
			if (numberOfCards < 1 || numberOfCards > 7) 
				throw new ArgumentOutOfRangeException("numberOfCards");
#endif
			uint clubs = (uint)((cards >> ClubsOffset) & SuitMask);
			uint diamonds = (uint)((cards >> DiamondsOffset) & SuitMask);
			uint hearts = (uint)((cards >> HeartsOffset) & SuitMask);
			uint spades = (uint)((cards >> SpadesOffset) & SuitMask);

			uint ranks = clubs | diamonds | hearts | spades;
			uint ranksCount = RankBitTable[ranks];
			uint duplCount = (uint)(numberOfCards - ranksCount);

			if (ranksCount >= 5)
			{
				// straight / straight flush
				if (RankBitTable[clubs] >= 5)
				{
					straight = StraightTable[clubs];
					if (straight != 0)
						return StraightFlushValue + (straight << TopCardOffset);
					result = FlushValue + TopFiveCardsTable[clubs];
				}
				else if (RankBitTable[diamonds] >= 5)
				{
					straight = StraightTable[diamonds];
					if (straight != 0)
						return StraightFlushValue + (straight << TopCardOffset);
					result = FlushValue + TopFiveCardsTable[diamonds];
				}
				else if (RankBitTable[hearts] >= 5)
				{
					straight = StraightTable[hearts];
					if (straight != 0)
						return StraightFlushValue + (straight << TopCardOffset);
					result = FlushValue + TopFiveCardsTable[hearts];
				}
				else if (RankBitTable[spades] >= 5)
				{
					straight = StraightTable[spades];
					if (straight != 0)
						return StraightFlushValue + (straight << TopCardOffset);
					result = FlushValue + TopFiveCardsTable[spades];
				}
				else
				{
					straight = StraightTable[ranks];
					if (straight != 0)
						result = StraightValue + (straight << TopCardOffset);
				}

				if (result != 0 && duplCount < 3)
					return result;
			}

			switch (duplCount)
			{
				case 0:
					return HighCardValue + TopFiveCardsTable[ranks];
				case 1:
					twoMask = ranks ^ (clubs ^ diamonds ^ hearts ^ spades);
					result = OnePairValue + (TopCardsTable[twoMask] << TopCardOffset);
					tmp = ranks ^ twoMask;
					kickers = (TopFiveCardsTable[tmp] >> CardWidth) & ~CardMask;
					result += kickers;
					return result;
				case 2:
					twoMask = ranks ^ (clubs ^ diamonds ^ hearts ^ spades);
					if (twoMask != 0)
					{
						tmp = ranks ^ twoMask;
						result = TwoPairsValue + (TopFiveCardsTable[twoMask] & (TopCardMask | SecondCardMask)) + (TopCardsTable[tmp] << ThirdCardOffset);
						return result;
					}
					else
					{
						threeMask = ((clubs & diamonds) | (hearts & spades)) & ((clubs & hearts) | (diamonds & spades));
						result = ThreeOfAKindValue + (TopCardsTable[threeMask] << TopCardOffset);
						tmp = ranks ^ threeMask;
						tmp2 = TopCardsTable[tmp];
						result += (tmp2 << SecondCardOffset);
						tmp ^= (1u << (int)tmp2);
						result += (TopCardsTable[tmp] << ThirdCardOffset);
						return result;
					}
				default:
					fourMask = hearts & diamonds & clubs & spades;
					if (fourMask != 0)
					{
						// quads
						tmp = TopCardsTable[fourMask];
						result = FourOfAKindValue + (tmp << TopCardOffset) + (TopCardsTable[ranks ^ (1u << (int)tmp)] << SecondCardOffset);
						return result;
					}

					twoMask = ranks ^ (clubs ^ diamonds ^ hearts ^ spades);
					if(RankBitTable[twoMask] != duplCount)
					{
						// full house
						threeMask = ((clubs & diamonds) | (hearts & spades)) & ((clubs & hearts) | (diamonds & spades));
						result = FullHouseValue;
						tmp = TopCardsTable[threeMask];
						result += (tmp << TopCardOffset);
						tmp2 = (twoMask | threeMask) ^ (1u << (int)tmp);
						result += (TopCardsTable[tmp2] << SecondCardOffset);
						return result;
					}

					if (result != 0)
						return result;

					result = TwoPairsValue;
					tmp = TopCardsTable[twoMask];
					result += tmp << TopCardOffset;
					tmp2 = TopCardsTable[twoMask ^ (1u << (int)tmp)];
					result += tmp2 << SecondCardOffset;
					result += (uint)((TopCardsTable[ranks ^ (1u << (int)tmp) ^ (1u << (int)tmp2)]) << ThirdCardOffset);
					return result;
			}
		}

		private static uint BuildTopFiveCards(ulong value, int rotated)
		{
			int nextRotate = 0;
			List<uint> values = new List<uint>();
			while (value > 0)
			{
				if ((value & 0x1) != 0)
					values.Add((uint)(nextRotate + rotated));
				value = value >> 1;
				nextRotate++;
			}
			uint result = 0;
			int start = 5 - values.Count;
			for (int c = 0; c < values.Count; c++)
				result += values[c] << (CardWidth * (start + c));
			return result;
		}

		static CardBit()
		{
			RankBitTable = new byte[SuitMask];
			StraightTable = new byte[SuitMask];
			TopFiveCardsTable = new uint[SuitMask];
			TopCardsTable = new uint[SuitMask];
			CardMaskTable = new ulong[NumberOfCards];
			ulong[] straightMask = new ulong[13 - 3];
			for(int c=0;c<straightMask.Length-1;c++)
				straightMask[c + 1] = (0x1fUL << c);
			straightMask[0] = 0x100f;
			for (ulong value = 0; value < SuitMask; value++)
			{
				byte count = (byte)NumberOfSetBits(value);
				RankBitTable[value] = count;
				if (count >= 5)
				{
					for (int c = straightMask.Length - 1; c >= 0; c--)
						if ((value & straightMask[c]) == straightMask[c])
						{
							StraightTable[value] = (byte)(c + 3);
							break;
						}
				}

				int rotated = 0;
				ulong nvalue = value;

				while (count > 5)
				{
					rotated++;
					nvalue = nvalue >> 1;
					count = (byte)NumberOfSetBits(nvalue);
				}
				TopFiveCardsTable[value] = BuildTopFiveCards(nvalue, rotated);
				while (nvalue != 0)
				{
					rotated++;
					nvalue = nvalue >> 1;
					count = (byte)NumberOfSetBits(nvalue);
				}
				TopCardsTable[value] = (uint)(rotated - 1);


			}
			int index = 0;
			foreach (Card card in Card.Cards)
				CardMaskTable[index++] = card.Mask;
		}
	}
}
